""" digging map generator 

heavily based on the C++ at www.jimrandomh.org/rldev
"""


from random import randint, random, choice

try:
    from euclid import Vector2
except ImportError:
    class Vector2(object):
        def __init__(self, x=0, y=0):
            self.x, self.y = x, y

        def __repr__(self):
            return '<%s x=%s y=%s>' % (self.__class__.__name__, self.x, self.y)

        def __add__(self, other):
            return Vec(self.x + other.x, self.y+other.y)

        def __sub__(self, other):
            return Vec(self.x - other.y, self.y-other.y)

        def __iadd__(self, other):
            self.x += other.x
            self.y += other.y
            return self

        def __isub__(self, other):
            self.x -= other.x
            self.y -= other.y
            return self

        def __mul__(self, other):
            return Vec(self.x * other, self.y * other)    

        def __eq__(self, other):
            return self.x == other.x and self.y == other.y
        
        def __neq__(self, other):
            return self.x != other.x or self.y != other.y

        def copy(self):
            return Vec(self.x, self.y)


class Vec(Vector2):
    def left(self):
        return Vec(self.y, -self.x)

    def right(self):
        return Vec(-self.y, self.x)

class Room(object):
    def __init__(self, pos, size):
        self.pos = pos
        self.size = size

class Corridor(Room):
    pass

class Doorway(object):
    def __init__(self, location, heading, door=True):
        self.location = location
        self.heading = heading
        self.has_door = door


class Digger(object):
    tiles = dict(
        unknown = 0,
        floor = 1,
        wall = 2,
        permwall = 3,
        door = 4,
    )

    def __init__(self, **opts):
        self.width = 79
        self.height = 30
        self.max_tries = 5
        self.room_min = 3
        self.room_max = 6
        self.corridor_min = 3
        self.corridor_max = 6
        self.room_chance = 0.5
        self.__dict__.update(opts)

    def __call__(self):
        self.grid = [[self.tiles['unknown'] for y in range(self.height)] 
                                            for x in range(self.width)]
        self.constructs = []
        self.doorways = []
        self.dig_loop()
        return self.grid, self.constructs

    def dig_loop(self):
        entrance = Vec(self.width / 2, self.height -1)
        entrance = Vec(randint(1, self.width-1), randint(1, self.height-1))
        dir = choice([
            (-1, 0),
            (1, 0),
            (0, -1),
            (0, 1)])
        self.doorways.append(Doorway(entrance, Vec(*dir)))

        self.door_tile(entrance)
        self.fill_tile(entrance+Vec(1, 0))
        self.fill_tile(entrance-Vec(1, 0))

        while self.doorways:
            which = randint(0, len(self.doorways)-1)
            door = self.doorways[which]
            del self.doorways[which]

            if self.dig_random(door.location, door.heading):
                if door.has_door:
                    self.door_tile(door.location)
                elif self.is_wall(door.location):
                    self.dig_tile(door.location)

    def dig_random(self, pos, heading):
        success = False
        tries = 0

        while tries < self.max_tries:
            if random() < self.room_chance:
                succ = self.dig_room(pos, heading)
            else:
                succ = self.dig_corridor(pos, heading)
            if succ:
                return True
            tries += 1

        return False

    def dig_room(self, entrance, heading):
        size = Vec(randint(self.room_min, self.room_max), 
                   randint(self.room_min, self.room_max))
        entrance_offset = randint(1, size.x)

        corner = entrance + (heading.left()*entrance_offset)
        pos = corner.copy()
        for y in range(size.y+2):
            for x in range(size.x+2):
                if not self.is_in_bounds_or_border(pos):
                    return False
                if not self.is_wall(pos) and pos != entrance:
                    return False
                pos += heading.right()
            pos -= heading.right()*(size.x+2)
            pos += heading

        pos = corner.copy()
        for y in range(size.y+2):
            for x in range(size.x+2):
                self.fill_tile(pos)
                pos += heading.right()   
            pos -= heading.right()*(size.x+2)
            pos += heading

        self.permwall_tile(corner)
        self.permwall_tile(corner+(heading.right()*(size.x+1)))
        self.permwall_tile(corner+(heading*(size.y-1)))
        self.permwall_tile(corner+(heading.right()*(size.x+1) + (heading*(size.y+1))))

        pos = corner+heading+heading.right()
        for y in range(size.y):
            for x in range(size.x):
                self.dig_tile(pos)
                pos += heading.right()
            pos -= heading.right()*size.x
            pos += heading
    
        self.door_tile(entrance)

        door_pos = corner + heading * randint(1, size.y)
        self.doorways.append(Doorway(door_pos, heading.left(), True))
        door_pos = corner + heading*(size.y+1) + heading.right() * randint(1, size.x)
        self.doorways.append(Doorway(door_pos, heading, True))
        door_pos = corner + heading.right()*(size.x+1) + heading*randint(1, size.y)
        self.doorways.append(Doorway(door_pos, heading.right(), True))

        self.constructs.append(Room(pos, size))

        return True

    def dig_corridor(self, entrance, heading):
        length = randint(self.corridor_min, self.corridor_max)
        found_intersect = False

        pos = entrance.copy()
        left_pos = entrance + heading.left()
        right_pos = entrance + heading.right()

        for i in range(length):
            pos += heading
            left_pos += heading
            right_pos += heading

            if not self.is_in_bounds(pos):
                return False
            if not self.is_wall(pos):
                found_intersect = True
                length = i
                break
            if (not self.is_wall(left_pos) or 
                not self.is_wall(right_pos) or 
                self.is_permwall(pos)):
                return False
        
        if length <= 1:
            return False
        if not found_intersect:
            return False

        pos = entrance.copy()
        left_pos = entrance + heading.left()
        right_pos = entrance + heading.right()

        for i in range(length):
            pos += heading
            left_pos += heading
            right_pos += heading

            self.dig_tile(pos)
            self.fill_tile(left_pos)
            self.fill_tile(right_pos)

            if not self.is_in_bounds(pos+heading):
                break
            if not self.is_wall(pos+heading):
                break


        if not self.is_in_bounds(pos+heading) or self.is_wall(pos+heading):
            self.fill_tile(pos)
            self.doorways.append(Doorway(pos, heading, false))
        else:
            self.door_tile(pos)

        self.constructs.append(Corridor(pos, i))

        return True


    def is_in_bounds(self, v):
        return v.x >= 1 and v.y >= 1 and v.x < self.width-1 and v.y < self.height-1

    def is_in_bounds_or_border(self, v):
        return v.x >= 0 and v.y >= 0 and v.x < self.width and v.y < self.height

    def is_known(self, v):
        return self.grid[v.x][v.y] != self.tiles['unknown']

    def is_floor(self, v):
        return self.grid[v.x][v.y] == self.tiles['floor']

    def is_wall(self, v):
        return self.grid[v.x][v.y] in (self.tiles['wall'], 
                                       self.tiles['unknown'], 
                                       self.tiles['permwall'])

    def is_permwall(self, v):
        return self.grid[v.x][v.y] == self.tiles['permwall']

    def dig_tile(self, v):
        self.grid[v.x][v.y] = self.tiles['floor']

    def door_tile(self, v):
        self.grid[v.x][v.y] = self.tiles['door']
    
    def fill_tile(self, v):
        self.grid[v.x][v.y] = self.tiles['wall']

    def permwall_tile(self, v):
        self.grid[v.x][v.y] = self.tiles['permwall']
    


if __name__ == '__main__':
    from pprint import pprint
    d = Digger(
        width=100, height=50, 
        #corridor_min=2, corridor_max=45,
        #room_min=3, room_max=10,
        #room_chance=0.2,
        max_tries=5,
    )
    grid, constructs = d()

    map = {
        d.tiles['unknown']: ' ',
        d.tiles['floor']: '.',
        d.tiles['wall']: '#',
        d.tiles['permwall']: '@',
        d.tiles['door']: '+',
    }

    pprint(constructs)

    for y in range(d.height):
        for x in range(d.width):
            print map[grid[x][y]], 
        print

   
